package edu.washington.cs.sounddetector.server.sound;

import jAudioFeatureExtractor.ACE.DataTypes.FeatureDefinition;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.builder.ToStringBuilder;

/**
 * <p>
 * SoundFeatures is a container for a set of feature values that have been extracted from a sound
 * and their corresponding definition objects.
 * </p>
 * 
 * <p>
 * SoundFeatures is immutable and threadsafe.
 * </p>
 * 
 * @author the.dylan.price@gmail.com
 * 
 */
public class SoundFeatures
{
    private final double[][] featureValues;
    private final FeatureDefinition[] featureDefinitions;
    private double[] flattenedFeatureValues;
    private String[] flattenedFeatureNames;

    /**
     * @param featureValues
     * @param featureDefinitions
     */
    public SoundFeatures(double[][] featureValues, FeatureDefinition[] featureDefinitions)
    {
        Validate.noNullElements(featureValues);
        Validate.noNullElements(featureDefinitions);

        this.featureValues = featureValues;
        this.featureDefinitions = featureDefinitions;

        flatten();

        assert ArrayUtils.isNotEmpty(this.flattenedFeatureValues);
        assert ArrayUtils.isNotEmpty(this.flattenedFeatureNames);
    }

    private void flatten()
    {
        List<String> featureNames = new LinkedList<String>();
        List<Double> compressedResultsList = new ArrayList<Double>();
        for (int i = 0; i < featureDefinitions.length; i++)
        {
            int dimensions = featureDefinitions[i].dimensions;
            for (int j = 0; j < dimensions; j++)
            {
                compressedResultsList.add(featureValues[i][j]);
                featureNames.add(featureDefinitions[i].name + j);
            }
        }

        assert compressedResultsList.size() == featureNames.size();

        flattenedFeatureValues = ArrayUtils.toPrimitive(compressedResultsList
                .toArray(ArrayUtils.EMPTY_DOUBLE_OBJECT_ARRAY));
        flattenedFeatureNames = featureNames.toArray(ArrayUtils.EMPTY_STRING_ARRAY);
    }

    /**
     * @return the feature values. The first index corresponds to the feature, the second index
     *         corresponds to each value for that feature.
     */
    public double[][] getFeatureValues()
    {
        return featureValues;
    }

    /**
     * @return the feature definitions. You can get the number of feature values for each feature in
     *         FeatureDefinition.dimensions.
     */
    public FeatureDefinition[] getFeatureDefinitions()
    {
        return featureDefinitions;
    }

    /**
     * @return the feature values matrix flattened into one long array of values. You can get the
     *         names of the features through {@link #getFlattenedFeatureNames()}.
     */
    public double[] getFlattenedFeatureValues()
    {
        return this.flattenedFeatureValues;
    }

    /**
     * @return the names of the features concatenated with the dimensions, e.g.
     *         {"Power Spectrum Overall Average0", "Power Spectrum Overall Average1", ...}. The
     *         indices in the array correspond to the feature values in
     *         {@link #getFlattenedFeatureValues()}.
     */
    public String[] getFlattenedFeatureNames()
    {
        return this.flattenedFeatureNames;
    }

    @Override
    public String toString()
    {
        return new ToStringBuilder(this)
            .append("flattenedFeatureValues", flattenedFeatureValues)
            .append("flattenedFeatureNames", flattenedFeatureNames).toString();
    }
}
